#pragma once
#ifndef OBR_BUILDER_H
#define OBR_BUILDER_H

#include "Background.h"
#include "Camera.h"
#include "Instance.h"
#include "Integrator.h"
#include "Light.h"
#include "Material.h"
#include "Medium.h"
#include "Mesh.h"
#include "common.h"

namespace OBR
{

class Scene;

template <typename T> struct BuildResult
{
    std::shared_ptr<T> object;
    std::function<void(std::shared_ptr<T>, Scene &)> callback; // TODO: improve callback
};

/**
 * @brief build an object of derived class and return as an refernce of core class object
 *
 * @tparam CoreClass one of the core classes
 * @tparam DerivedClass any derived class from given core class
 * @param config json config value node
 * @return CoreClass object built
 */
template <typename CoreClass, typename DerivedClass>
BuildResult<CoreClass> builder(const Json::Value &config)
{
    // building
    std::shared_ptr<DerivedClass> obj = std::make_shared<DerivedClass>();
    obj->buildFromConfig(config);
    // setting result
    BuildResult<CoreClass> result;
    result.callback = [](std::shared_ptr<CoreClass> pthis, Scene &scene) {
        std::dynamic_pointer_cast<DerivedClass>(pthis)->postBuild(scene);
    };
    result.object = obj;
    return result;
};

/* ---------------------------- factory template ---------------------------- */

template <typename CoreClass> class OBRFactory
{
private:
public:
    static std::map<std::string, std::function<BuildResult<CoreClass>(const Json::Value &)>>
        mapping;
    /**
     * @brief register a direved class builder function to factory
     *
     * @param type
     * @param builder
     */
    static inline void registerClass(
        const std::string &type,
        const std::function<BuildResult<CoreClass>(const Json::Value &)> &builder)
    {
        OBRFactory<CoreClass>::mapping[type] = builder;
    }

    /**
     * @brief generate object from given type and config
     *
     * @param type
     * @param config
     * @return CoreClass&
     */
    static inline BuildResult<CoreClass> fromJsonConfig(const std::string type,
                                                        const Json::Value config)
    {
        return OBRFactory<CoreClass>::mapping[type](config);
    }
};

template <typename CoreClass>
std::map<std::string, std::function<BuildResult<CoreClass>(const Json::Value &)>>
    OBRFactory<CoreClass>::mapping = {};

/* ----------------------------- core factories ----------------------------- */

typedef OBRFactory<Light> LightFactory;
typedef OBRFactory<Mesh> MeshFactory;
typedef OBRFactory<Medium> MediumFactory;
typedef OBRFactory<Material> MaterialFactory;
typedef OBRFactory<Camera> CameraFactory;
typedef OBRFactory<Background> BackgroundFactory;
typedef OBRFactory<Instance> InstanceFactory;
typedef OBRFactory<Integrator> IntergratorFactory;

} // namespace OBR

#endif // OBR_BUILDER_H
